package com.topisv.tms.service.zto;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springside.modules.mapper.JsonMapper;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.hash.Hashing;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * 中通电子面单工具类
 * 
 * @author tinsang
 * @since 2015-08-06
 * @version 1.0
 */
public class ZTOUtils {

    public final static Logger     LOGGER                  = LoggerFactory.getLogger(ZTOUtils.class);
    public final static Charset    UTF_8                   = Charsets.UTF_8;
    public final static String     CHARSET_STR             = UTF_8.name();
    public final static JsonMapper jsonMapper              = JsonMapper.nonDefaultMapper();
    static String                  URL                     = "http://partner.zto.cn/client/interface.php";
    static String                  STYLE                   = "json";
    private static final String    FUNC_MAIL_TRACE         = "mail.trace";                                                                                                                     // 签收图片接口
    private static final String    FUNC_MAIL_BILLIMG       = "mail.billimg";                                                                                                                   // 快件追踪接口
    private static final String    FUNC_NAIL_STATUS        = "mail.status";
    private static final String    FUNC_MAIL_COUNTER       = "mail.counter";
    private static final String    FUNC_ORDER_SUBMIT       = "order.submit";
    private static final String    FUNC_ORDER_BATCH_SUBMIT = "order.batch_submit";
    private static final String    FUNC_ORDER_CANCEL       = "order.cancel";
    private static final String    FUNC_ORDER_MARKE        = "order.marke";

    /**
     * 签收图片接口
     * 
     * @param mailno 运单号
     * @param partner 合作账号
     * @param pwd 合作账号密码
     * @return
     * @throws IOException
     */
    public static String billimg(final String mailno, final String partner, final String pwd) throws IOException {

        checkArgument(StringUtils.isNotEmpty(mailno), String.format("mailno is empty"));
        checkArgument(StringUtils.isNotEmpty(partner), String.format("partner is empty"));
        checkArgument(StringUtils.isNotEmpty(pwd), String.format("pwd is empty"));

        Map<String, String> contentMap = Maps.newHashMap();
        contentMap.put("mailno", mailno);
        String content = jsonMapper.toJson(contentMap);
        return post(URL, getParamMap(content, FUNC_MAIL_BILLIMG, partner, pwd));
    }

    /**
     * 快件追踪接口
     * 
     * @param mailno 运单号
     * @param partner 合作账号
     * @param pwd 合作账号密码
     * @return
     * @throws IOException
     */
    public static String trace(final String mailno, final String partner, final String pwd) throws IOException {

        checkArgument(StringUtils.isNotEmpty(mailno), String.format("mailno is empty"));
        checkArgument(StringUtils.isNotEmpty(partner), String.format("partner is empty"));
        checkArgument(StringUtils.isNotEmpty(pwd), String.format("pwd is empty"));

        Map<String, String> contentMap = Maps.newHashMap();
        contentMap.put("mailno", mailno);
        String content = jsonMapper.toJson(contentMap);
        return post(URL, getParamMap(content, FUNC_MAIL_TRACE, partner, pwd));
    }

    /**
     * @param mailno
     * @param isall
     * @param partner
     * @param pwd
     * @return
     * @throws IOException
     */
    public static String status(final String mailno, final boolean isall, final String partner, final String pwd)
                                                                                                                 throws IOException {
        checkArgument(StringUtils.isNotEmpty(mailno), String.format("mailno is empty"));
        checkArgument(StringUtils.isNotEmpty(partner), String.format("partner is empty"));
        checkArgument(StringUtils.isNotEmpty(pwd), String.format("pwd is empty"));

        Map<String, Object> contentMap = Maps.newHashMap();
        contentMap.put("mailno", mailno);
        contentMap.put("isall ", isall);
        String content = jsonMapper.toJson(contentMap);
        return post(URL, getParamMap(content, FUNC_NAIL_STATUS, partner, pwd));
    }

    /**
     * 电子运单号可用数量查询
     * 
     * @param partner
     * @param pwd
     * @return
     * @throws IOException
     */
    public static int counter(final String partner, final String pwd) throws IOException {
        return counter("", partner, pwd);
    }

    /**
     * 电子运单号可用数量查询
     * 
     * @param lastno 已申请过的最后一个运单号码。 （如提供此单号就以此单号开始统计未使用单号， 如不提供就查询所有未使用单号）。
     * @param partner
     * @param pwd
     * @return
     * @throws IOException
     */
    public static int counter(final String lastno, final String partner, final String pwd) throws IOException {

        checkArgument(StringUtils.isNotEmpty(partner), String.format("partner is empty"));
        checkArgument(StringUtils.isNotEmpty(pwd), String.format("pwd is empty"));

        Map<String, Object> contentMap = Maps.newHashMap();
        contentMap.put("lastno", lastno == null ? "" : lastno);
        String content = jsonMapper.toJson(contentMap);
        String returnData = post(URL, getParamMap(content, FUNC_MAIL_COUNTER, partner, pwd));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("counter returndata={}", returnData);
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> mapJson = jsonMapper.fromJson(returnData, Map.class);
        if (Boolean.valueOf(String.valueOf(mapJson.get("result")))) {
            @SuppressWarnings("unchecked")
            Map<String, Object> counterJsonMap = (Map<String, Object>) mapJson.get("counter");
            return Integer.parseInt(((String) counterJsonMap.get("available")));
        } else {
            throw new IOException(String.format("ERROR ZTO returnData=%s", returnData));
        }
    }

    /**
     * 批量下单
     * 
     * @param listZTOOrderBean
     * @param partner
     * @param pwd
     * @return
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public static boolean submitBatch(final List<ZTOOrderBean> listZTOOrderBean, final String partner, final String pwd)
                                                                                                                        throws IOException {
        checkArgument(listZTOOrderBean != null && listZTOOrderBean.size() > 0,
            String.format("listZTOOrderBean is empty"));
        checkArgument(StringUtils.isNotEmpty(partner), String.format("partner is empty"));
        checkArgument(StringUtils.isNotEmpty(pwd), String.format("pwd is empty"));

        List<Map<String, Object>> listMap = Lists.newArrayList();
        for (ZTOOrderBean bean : listZTOOrderBean) {
            checkArgumentBean(bean);
            listMap.add(getSubmitParamMap(bean));
        }
        String content = jsonMapper.toJson(listMap);
        String returnData = post(URL, getParamMap(content, FUNC_ORDER_BATCH_SUBMIT, partner, pwd));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("submitBatch returndata={}", returnData);
        }

        Map<String, Object> mapJson = jsonMapper.fromJson(returnData, Map.class);
        if (Boolean.valueOf(String.valueOf(mapJson.get("result")))) {
            List<Map<String, Object>> listData = (List<Map<String, Object>>) mapJson.get("keys");
            for (Map<String, Object> m : listData) {
                String result = (String) m.get("result");
                if (!"true".equals(result)) {
                    throw new IOException(String.format("ZTO result false %s", m));
                }
                String orderId = (String) m.get("orderid");
                String mailno = (String) m.get("mailno");
                String mark = (String) m.get("mark");
                String sitename = (String) m.get("sitename");
                L: for (ZTOOrderBean bean : listZTOOrderBean) {
                    if (bean.getPaiuOrderId().equals(orderId)) {
                        bean.setZtoMailno(mailno);
                        bean.setMaker(mark);
                        bean.setSitename(StringUtils.isEmpty(sitename) ? "广州市场部" : sitename);
                        break L;
                    }
                }
            }
            return true;
        }
        return false;
    }

    /**
     * @param bean
     */
    private static void checkArgumentBean(ZTOOrderBean bean) {
        checkArgument(StringUtils.isNotEmpty(bean.getPaiuOrderId()), String.format("paiuOrderId is empty"));
        checkArgument(StringUtils.isNotEmpty(bean.getSenderName()), String.format("senderName is empty"));
        checkArgument(StringUtils.isNotEmpty(bean.getSenderMobile()) || StringUtils.isNotEmpty(bean.getSenderPhone()),
            String.format("senderMobile && senderMobile is empty"));
        checkArgument(StringUtils.isNotEmpty(bean.getSenderCity()), String.format("senderCity is empty"));
        checkArgument(StringUtils.isNotEmpty(bean.getSenderAddress()), String.format("senderAddress is empty"));

        checkArgument(StringUtils.isNotEmpty(bean.getReceiverName()), String.format("receiverName is empty"));
        checkArgument(StringUtils.isNotEmpty(bean.getReceiverMobile())
                      || StringUtils.isNotEmpty(bean.getReceiverPhone()),
            String.format("receiverMobile && receiverPhone is empty"));
        checkArgument(StringUtils.isNotEmpty(bean.getReceiverCity()), String.format("receiverCity is empty"));
        checkArgument(StringUtils.isNotEmpty(bean.getReceiverAddress()), String.format("receiverAddress is empty"));
    }

    /**
     * @param bean
     * @return
     */
    private static Map<String, Object> getSubmitParamMap(ZTOOrderBean bean) {
        Map<String, Object> senderMap = Maps.newHashMap();
        senderMap.put("name", bean.getSenderName());
        senderMap.put("mobile", bean.getSenderMobile() == null ? "" : bean.getSenderMobile());
        senderMap.put("phone", bean.getSenderPhone() == null ? "" : bean.getSenderPhone());
        senderMap.put("city", bean.getSenderCity());
        senderMap.put("address", bean.getSenderAddress());

        Map<String, Object> receiverMap = Maps.newHashMap();
        receiverMap.put("name", bean.getReceiverName());
        receiverMap.put("mobile", bean.getReceiverMobile() == null ? "" : bean.getReceiverMobile());
        receiverMap.put("phone", bean.getReceiverPhone() == null ? "" : bean.getReceiverPhone());
        receiverMap.put("city", bean.getReceiverCity());
        receiverMap.put("address", bean.getReceiverAddress());

        Map<String, Object> contentMap = Maps.newHashMap();
        contentMap.put("id", bean.getPaiuOrderId());
        contentMap.put("mailno", bean.getZtoMailno() == null ? "" : bean.getZtoMailno());
        contentMap.put("sender", senderMap);
        contentMap.put("receiver", receiverMap);
        return contentMap;
    }

    /**
     * 新增或修改订单
     * 
     * @param bean
     * @param partner
     * @param pwd
     * @return
     * @throws IOException
     */
    public static boolean submit(ZTOOrderBean bean, final String partner, final String pwd) throws IOException {
        checkArgumentBean(bean);
        checkArgument(StringUtils.isNotEmpty(partner), String.format("partner is empty"));
        checkArgument(StringUtils.isNotEmpty(pwd), String.format("pwd is empty"));

        String content = jsonMapper.toJson(getSubmitParamMap(bean));
        String returnData = post(URL, getParamMap(content, FUNC_ORDER_SUBMIT, partner, pwd));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("submit returndata={}", returnData);
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> mapJson = jsonMapper.fromJson(returnData, Map.class);
        if (Boolean.valueOf(String.valueOf(mapJson.get("result")))) {
            @SuppressWarnings("unchecked")
            Map<String, Object> mapData = (Map<String, Object>) mapJson.get("keys");
            String mailno = (String) mapData.get("mailno");
            String mark = (String) mapData.get("mark");
            String sitename = (String) mapData.get("sitename");
            bean.setZtoMailno(mailno);
            bean.setMaker(mark);
            bean.setSitename(sitename);
            return true;
        }
        return false;
    }

    /**
     * 订单取消
     * 
     * @param orderId 订单ID
     * @param partner
     * @param pwd
     * @return
     * @throws IOException
     */
    public static boolean cancel(final String orderId, final String partner, final String pwd) throws IOException {

        checkArgument(StringUtils.isNotEmpty(orderId), String.format("orderId is empty"));

        Map<String, Object> contentMap = Maps.newHashMap();
        contentMap.put("id", orderId);
        String content = jsonMapper.toJson(contentMap);
        String returnData = post(URL, getParamMap(content, FUNC_ORDER_CANCEL, partner, pwd));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("ZTO cancel returndata={}", returnData);
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> mapJson = jsonMapper.fromJson(returnData, Map.class);
        return Boolean.valueOf(String.valueOf(mapJson.get("result")));
    }

    /**
     * @param sendcity
     * @param receivercity
     * @param partner
     * @param pwd
     * @return
     * @throws IOException
     */
    public static String maker(final String sendcity, final String receivercity, final String partner, final String pwd)
                                                                                                                        throws IOException {
        checkArgument(StringUtils.isNotEmpty(sendcity), String.format("sendcity is empty"));
        checkArgument(StringUtils.isNotEmpty(receivercity), String.format("receivercity is empty"));
        checkArgument(StringUtils.isNotEmpty(partner), String.format("partner is empty"));
        checkArgument(StringUtils.isNotEmpty(pwd), String.format("pwd is empty"));

        Map<String, Object> contentMap = Maps.newHashMap();
        contentMap.put("sendcity", sendcity);
        contentMap.put("receivercity", receivercity);
        String content = jsonMapper.toJson(contentMap);
        String returnData = post(URL, getParamMap(content, FUNC_ORDER_MARKE, partner, pwd));

        if (StringUtils.isEmpty(returnData)) {
            LOGGER.error("ZTO maker return empty,param={}", content);
            return "";
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> mapJson = jsonMapper.fromJson(returnData, Map.class);
        if (mapJson.containsKey("marke")) {
            return String.valueOf(mapJson.get("marke"));
        } else {
            LOGGER.error("ERROR,ZTO maker return data={},param={}", mapJson, content);
            return "";
        }
    }

    /**
     * @param func
     * @param content
     * @param partner
     * @param pwd
     * @return
     */
    private static Map<String, String> getParamMap(final String content, final String func, final String partner,
                                                   final String pwd) {
        String _content = encryptBASE64(content);
        String datetime = formatDateNow();
        Map<String, String> paramMap = Maps.newHashMap();
        paramMap.put("content", _content);
        paramMap.put("style", STYLE);
        paramMap.put("func", func);
        paramMap.put("partner", partner);
        paramMap.put("datetime", datetime);
        paramMap.put("verify", digestMD5(partner + datetime + _content + pwd));
        return paramMap;
    }

    /**
     * @return
     */
    private static String formatDateNow() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(new Date());
    }

    /**
     * 将map转换为请求字符串
     * <p>
     * data=xxx&msg_type=xxx
     * </p>
     * 
     * @param params
     * @param charset
     * @return
     */
    private static String buildQuery(Map<String, String> params) {

        if (params == null || params.isEmpty()) {
            return null;
        }

        StringBuilder data = new StringBuilder();
        boolean flag = false;
        for (Entry<String, String> entry : params.entrySet()) {
            if (flag) {
                data.append("&");
            } else {
                flag = true;
            }
            data.append(entry.getKey()).append("=").append(urlEncode(entry.getValue()));
        }
        return data.toString();
    }

    /**
     * @param data
     * @return
     */
    private static String urlEncode(String data) {
        try {
            return URLEncoder.encode(data, CHARSET_STR);
        } catch (UnsupportedEncodingException e) {
            return data;
        }
    }

    /**
     * @param data
     * @return
     */
    private static String digestMD5(String data) {
        return Hashing.md5().hashString(data, UTF_8).toString();
    }

    /**
     * @param data
     * @return
     */
    private static String encryptBASE64(String data) {
        return Base64.encodeBase64String(data.getBytes(UTF_8));
    }

    /**
     * 发送POST 请求
     * 
     * @param url 请求地址
     * @param charset 编码格式
     * @param params 请求参数
     * @return 响应
     * @throws IOException
     */
    private static String post(String url, Map<String, String> params) throws IOException {

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("post url={},param={}", url, params);
        }

        HttpURLConnection conn = null;
        OutputStreamWriter out = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader reader = null;
        StringBuilder result = new StringBuilder();
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(3000);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("Accept-Charset", CHARSET_STR);
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            out = new OutputStreamWriter(conn.getOutputStream(), CHARSET_STR);
            out.write(buildQuery(params));
            out.flush();
            inputStream = conn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, CHARSET_STR);
            reader = new BufferedReader(inputStreamReader);
            String tempLine = null;
            while ((tempLine = reader.readLine()) != null) {
                result.append(tempLine);
            }
        } finally {
            if (out != null) {
                out.close();
            }
            if (reader != null) {
                reader.close();
            }
            if (inputStreamReader != null) {
                inputStreamReader.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }
        return result.toString();
    }

    public static void main(String[] args) throws IOException {
        ZTOUtils.URL = "http://testpartner.zto.cn/client/interface.php";
        final String paiuOrderId = "PD2324335rtf11";
        final String senderName = "Test";
        final String senderMobile = "13454567898";
        final String senderPhone = "23453333";
        final String senderCity = "广东深圳南山区";
        final String senderAddress = "科技园科兴科学园";
        final String receiverName = "tinsang";
        final String receiverMobile = "15823454321";
        final String receiverPhone = "";
        final String receiverCity = "广东深圳南山区";
        final String receiverAddress = "西丽街道办";
        final String partner = "test";
        final String pwd = "ZTO123";
        ZTOOrderBean bean = new ZTOOrderBean();
        bean.setPaiuOrderId(paiuOrderId);

        bean.setReceiverAddress(receiverAddress);
        bean.setReceiverCity(receiverCity);
        bean.setReceiverMobile(receiverMobile);
        bean.setReceiverName(receiverName);
        bean.setReceiverPhone(receiverPhone);

        bean.setSenderAddress(senderAddress);
        bean.setSenderCity(senderCity);
        bean.setSenderMobile(senderMobile);
        bean.setSenderName(senderName);
        bean.setSenderPhone(senderPhone);

        System.out.println(submit(bean, partner, pwd));

        String sendcity = "上海市,上海市,虹口区";
        String receivercity = "湖北省,仙桃,仙桃";
        String returnData = maker(sendcity, receivercity, partner, pwd);
        System.out.println(returnData);

        System.out.println(counter(partner, pwd));
        System.out.println(cancel("PD2324335rtfD1", partner, pwd));
    }
}
